package nl.cypherpunk.proLearner.mealyDot;

import net.automatalib.words.Word;
import nl.cypherpunk.proLearner.fuzzing.TransitionRecord;

import java.io.*;
import java.util.*;

import static nl.cypherpunk.proLearner.Common.deepCopy;

public class MealyDot {
    private String fileName;
    private Set<MealyState> states;
    private MealyState initialState;
    private Set<String> input;
    private Set<String> output;
    List<Word<String>> accessSeq;
    public MealyDot(String dotFilename){
        this.fileName = dotFilename;
        states = new LinkedHashSet<>();
        input = new LinkedHashSet<>();
        output = new LinkedHashSet<>();
        getStatesAndInitialState();
        getTransitions();
    }

    private void getStatesAndInitialState(){
        try (BufferedReader br = new BufferedReader(new FileReader(fileName))) {
            String line;
            while ((line = br.readLine()) != null) {
                line = line.replaceAll("\\s+","");
                if(line.contains("s") && line.contains("shape=\"circle\"label=")){
                    states.add(new MealyState(line.split("\\[")[0]));
                }

                if(line.contains("__start") && line.contains("->")){
                    String stateName = line.split("->")[1];
                    stateName = stateName.substring(0, stateName.length() - 1);
                    for (MealyState state: states) {
                        if(stateName.equals(state.getName())){
                            this.initialState = state;
                            break;
                        }
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    private void getTransitions(){
        try (BufferedReader br = new BufferedReader(new FileReader(fileName))) {
            String line;
            while ((line = br.readLine()) != null) {
                line = line.replaceAll("\\s+","");
                if(line.contains("->") && !line.contains("__start")){
                    System.out.println(line);
                    String[] states = line.split("->");
                    MealyState stateFrom = getState(states[0]);
                    MealyState stateTo = getState(states[1].split("\\[")[0]);
                    String transitionString = line.substring(line.indexOf("=\"")+2,line.indexOf("\"]"));
                    String[] transitions = transitionString.split("/");
                    assert stateFrom != null;
                    stateFrom.addTransition(transitions[0], transitions[1], stateTo);
                    input.add(transitions[0]);
                    output.add(transitions[1]);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private MealyState getState(String name){
        for (MealyState state: this.states) {
            if(state.getName().equals(name))
                return state;
        }
        return null;
    }

    public List<String> traverseInput(List<String> inputs){
        MealyState currentState = initialState;
        List<String> outputs = new ArrayList<>();
        for (String input: inputs) {
            assert currentState != null;
            OutputTransition outputTransition = currentState.getOutputTransition(input);
            currentState = getState(outputTransition.state.getName());
            outputs.add(outputTransition.output);
        }
        return outputs;
    }

    public MealyState getNextState(MealyState currState, String input){
        OutputTransition outputTransition = currState.getOutputTransition(input);
        return getState(outputTransition.state.getName());
    }

    public String getFileName() {
        return fileName;
    }

    public MealyState getInitialState(){
        return initialState;
    }

    public int getStateSize(){ return states.size();}

    public Set<String> getInputAlphabet(){ return this.input;}

    public Set<String> getOutputAlphabet(){ return this.output;}

    public String getOutput(MealyState currState, String input){
        return currState.getOutputTransition(input).output;
    }

    public Set<MealyState> getStates(){
        try{
            analyzeAccess();
        }catch(Exception e)
        {
            e.printStackTrace();
        }
        return states;
    }

    public Integer getStatesNum(){
        return states.size();
    }

    public MealyState getSinkedState(){
        for (MealyState tmp: states) {
            int i = 0;
            for(String symbol : input){
                i++;
                if(!getOutput(tmp,symbol).contains("ConnectionClosed"))
                    break;
                if(i == input.size()-1)
                    return tmp;
            }
        }
        return null;
    }

    public void analyzeAccess() throws Exception
    {
        accessSeq = new ArrayList<>();
        Queue<TransitionRecord> bfsQueue = new ArrayDeque<>();
        List<MealyState> initList = new LinkedList<MealyState>();
        initList.add(initialState);
        bfsQueue.add(new TransitionRecord(initialState, Word.epsilon(),Word.epsilon(), new LinkedList<byte[]>(),initList));
        TransitionRecord curr = bfsQueue.poll();
        Word<String> tmp = Word.epsilon();
        accessSeq.add(tmp);
        int j = 0;
        String tmpOutput="";
        String sinkedStateName="";
        List<MealyState> visited = new ArrayList<>();
        visited.add(initialState);
        MealyState sinkedState=getSinkedState();
        if(sinkedState!=null)
            sinkedStateName = sinkedState.getName();
        while(curr != null)
        {
            MealyState currState = curr.state;
            for(int i = 0; i < currState.getInputs().size(); i++)
            {
                List<MealyState> stateList = deepCopy(curr.stateList);
                String input = currState.getInputs().get(i);
                OutputTransition outputTransition = currState.getOutputTransition(input);
                MealyState targetState = outputTransition.state;
                if(targetState != null ){
                    tmpOutput = outputTransition.output;
                    if((stateContain(stateList,targetState) || targetState.getName().equals(sinkedStateName)) && !tmpOutput.contains("ConnectionClosed"))
                    {
                        stateList.add(targetState);
                        Word<String> inputList = curr.accessSequence.append(input);
                        j++;
                    }
                    else if(!stateContain(visited,targetState))
                    {
                        stateList.add(targetState);
                        Word<String> inputList = curr.accessSequence.append(input);
                        Word<String> outputList = curr.outputSequence.append(tmpOutput);
                        TransitionRecord newRecord = new TransitionRecord(targetState,inputList,outputList,new LinkedList<byte[]>(),stateList);
                        j++;
                        bfsQueue.add(newRecord);
                        visited.add(targetState);
                        accessSeq.add(inputList);
                    }
                }
            }
            curr = bfsQueue.poll();
        }
        bfsQueue.clear();
        bfsQueue = null;
        states = new LinkedHashSet<>(visited);
    }

    public List<Word<String>> getAccess()
    {
        try{
            analyzeAccess();
        }catch(Exception e)
        {
            e.printStackTrace();
        }
        return accessSeq;
    }

    public boolean stateContain(List<MealyState> stateList, MealyState targetState)
    {
        for (MealyState tmp : stateList ) {
            if(tmp.getName().equals(targetState.getName()))
                return true;
        }
        return false;
    }

    public void updateMealy(String subtype) {
        for (MealyState tmp : states) {
            tmp.delTransition(subtype);
        }
        input.remove(subtype);
        try {
            analyzeAccess();
        } catch (Exception e)
        {
            e.printStackTrace();
        }
        updateFile(subtype);
    }

    public void updateFile(String subtype){
        String update = "";
        try (BufferedReader br = new BufferedReader(new FileReader(fileName))) {
            String line;
            while ((line = br.readLine()) != null) {
                if(!line.contains(subtype)){
                    update = update.concat(line+"\r\n");
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        try (BufferedWriter bw = new BufferedWriter(new FileWriter(fileName))) {
            bw.write(update);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void writeFile(String newFile){
        String update = "";
        try (BufferedReader br = new BufferedReader(new FileReader(fileName))) {
            String line;
            while ((line = br.readLine()) != null) {
                update = update.concat(line+"\r\n");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        try (BufferedWriter bw = new BufferedWriter(new FileWriter(newFile))) {
            bw.write(update);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}




